﻿using System;
using System.ComponentModel;
using System.Drawing.Design;

namespace CSharpGL
{
    /// <summary>
    /// VAO是用来管理VBO的。可以进一步减少DrawCall。
    /// <para>VAO is used to reduce draw-call.</para>
    /// </summary>
    [Editor(typeof(PropertyGridEditor), typeof(UITypeEditor))]
    public sealed class VertexArrayObject : IDisposable
    {
        internal static readonly GLDelegates.void_int_uintN glGenVertexArrays;
        internal static readonly GLDelegates.void_uint glBindVertexArray;
        internal static readonly GLDelegates.void_int_uintN glDeleteVertexArrays;
        static VertexArrayObject()
        {
            glGenVertexArrays = GL.Instance.GetDelegateFor("glGenVertexArrays", GLDelegates.typeof_void_int_uintN) as GLDelegates.void_int_uintN;
            glBindVertexArray = GL.Instance.GetDelegateFor("glBindVertexArray", GLDelegates.typeof_void_uint) as GLDelegates.void_uint;
            glDeleteVertexArrays = GL.Instance.GetDelegateFor("glDeleteVertexArrays", GLDelegates.typeof_void_int_uintN) as GLDelegates.void_int_uintN;

        }
        private const string strVertexArrayObject = "Vertex Array Object";

        /// <summary>
        /// vertex attribute buffers('in vec3 position;' in shader etc.)
        /// </summary>
        [Category(strVertexArrayObject)]
        [Description("vertex attribute buffers('in vec3 position;' in shader etc.)")]
        public VertexShaderAttribute[] VertexAttributes { get; private set; }

        /// <summary>
        /// The draw command.
        /// </summary>
        [Category(strVertexArrayObject)]
        [Description("The one and only one index buffer used to indexing vertex attribute buffers.)")]
        public IDrawCommand DrawCommand { get; private set; }

        private uint[] ids = new uint[1];

        /// <summary>
        /// 此VAO的ID，由OpenGL给出。
        /// <para>Id generated by glGenVertexArrays().</para>
        /// </summary>
        [Category(strVertexArrayObject)]
        [Description("Id generated by glGenVertexArrays().")]
        public uint Id { get { return ids[0]; } }

        /// <summary>
        /// VAO是用来管理VBO的。可以进一步减少DrawCall。
        /// <para>VAO is used to reduce draw-call.</para>
        /// </summary>
        /// <param name="drawCommand">index buffer pointer that used to invoke draw command.</param>
        /// <param name="shaderProgram">shader program that <paramref name="vertexAttributes"/> bind to.</param>
        /// <param name="vertexAttributes">给出此VAO要管理的所有VBO。<para>All VBOs that are managed by this VAO.</para></param>
        public VertexArrayObject(IDrawCommand drawCommand, ShaderProgram shaderProgram, params VertexShaderAttribute[] vertexAttributes)
        {
            if (drawCommand == null)
            {
                throw new ArgumentNullException("drawCommand");
            }
            // Zero vertex attribute is allowed in GLSL.
            //if (vertexAttributeBuffers == null || vertexAttributeBuffers.Length == 0)
            //{
            //    throw new ArgumentNullException("vertexAttributeBuffers");
            //}

            this.DrawCommand = drawCommand;
            this.VertexAttributes = vertexAttributes;

            glGenVertexArrays(1, ids);

            glBindVertexArray(this.Id); // this vertex array object will record all stand-by actions.

            foreach (var item in vertexAttributes)
            {
                VertexBuffer buffer = item.Buffer;
                buffer.Standby(shaderProgram, item.VarNameInVertexShader);
            }

            glBindVertexArray(0); // this vertex array object has recorded all stand-by actions.
        }

        internal void Bind()
        {
            glBindVertexArray(this.Id);
        }

        internal void Unbind()
        {
            glBindVertexArray(0);
        }

        /// <summary>
        /// 执行一次渲染的过程。
        /// <para>Execute rendering command.</para>
        /// </summary>
        /// <param name="temporaryIndexBuffer">render by a temporary index buffer</param>
        public void Draw(IDrawCommand temporaryIndexBuffer = null)
        {
            this.Bind();

            if (temporaryIndexBuffer != null)
            {
                temporaryIndexBuffer.Draw();
            }
            else
            {
                this.DrawCommand.Draw();
            }

            this.Unbind();
        }

        /// <summary>
        ///
        /// </summary>
        public override string ToString()
        {
            return string.Format("VAO Id: {0}", this.Id);
        }

        /// <summary>
        ///
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        ///
        /// </summary>
        ~VertexArrayObject()
        {
            this.Dispose(false);
        }

        private bool disposedValue;

        private void Dispose(bool disposing)
        {
            if (this.disposedValue == false)
            {
                if (disposing)
                {
                    // Dispose managed resources.
                }

                // Dispose unmanaged resources.

                IntPtr ptr = GL.Instance.GetCurrentContext(); //Win32.wglGetCurrentContext();
                if (ptr != IntPtr.Zero)
                {
                    {
                        var fucntion = GL.Instance.GetDelegateFor("glDeleteVertexArrays", GLDelegates.typeof_void_int_uintN) as GLDelegates.void_int_uintN;
                        fucntion(1, this.ids);
                        this.ids[0] = 0;
                    }
                    {
                        // NOTE: This indicates that all references to these VertexShaderAttribute objects should be disposed.
                        VertexShaderAttribute[] vertexAttributeBuffers = this.VertexAttributes;
                        if (vertexAttributeBuffers != null)
                        {
                            foreach (var item in vertexAttributeBuffers)
                            {
                                item.Buffer.Dispose();
                            }
                        }
                    }
                    {
                        var disp = this.DrawCommand as IDisposable;
                        if (disp != null) { disp.Dispose(); }
                    }
                }
            }

            this.disposedValue = true;
        }
    }
}